﻿<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta charset="utf-8"/>
		<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
		<title>Объектно-ориентированное программирование</title>

		<link rel="stylesheet" type="text/css" href="http://moodle.spb.ru/file.php/84/python/standby.css"/><!-- page style: default -->
	</head>
	<body>
		<div class='page'>
			<h2>Объектно-ориентированное программирование</h2>
			<p>
				Перед тем, как более подробно рассмотреть стандартную библиотеку модулей, необходимо познакомиться с возможностями языка Python в области объектно-ориентированного программирования, поскольку наряду с различными константами и функциями модуль может содержать различного рода объекты, с которыми нужно уметь работать.
			</p>
			<p>
				Python проектировался как объектно-ориентированный язык программирования. Это означает (по Алану Кэю, автору объектно-ориентированного языка Smalltalk), что он построен с учётом следующих принципов:
			</p>
			<ul type="circle">
				<li>Все данные в нём представляются объектами</li>
				<li>Программу можно составить как набор взаимодействующих объектов, посылающих друг другу сообщения</li>
				<li>Каждый объект имеет собственную часть памяти и может состоять из других объектов</li>
				<li>Каждый объект имеет тип</li>
				<li>Все объекты одного типа могут принимать одни и те же сообщения (и выполнять одни и те же действия)</li>
			</ul>
			<div class="note">
				<p>
					До этого момента объекты Python встречались много раз, поскольку <span class="important">каждое число, строка, последовательность и даже функция - это объекты</span>. Некоторые встроенные объекты имеют в Python синтаксическую поддержку - например, задание литералов для строк, списков, кортежей, словарей. Но Python, наряду с использованием встроенных, позволяет создавать и новые объекты.
				</p>
			</div>
			<p>
				Говоря об ООП, нельзя не упомянуть о следующих понятиях:
			</p>
			<ul type="circle">			
				<li>Инкапсуляция</li>
				<li>Наследование</li>
				<li>Полиморфизм</li>
			</ul>
			<p>
				Под <a href="http://ru.wikipedia.org/wiki/Инкапсуляция_(программирование)" title="Посмотреть в ВИКИ">инкапсуляцией</a> понимается сокрытие информации о внутреннем устройстве объекта. Если приводить пример, то это очень похоже на управление автомобилем - вы можете управлять им с помощью руля, педалей газа, сцепления, тормоза, коробки передач, но вы не знаете как именно устроен автомобиль, что у него внутри и как это всё работает.
			</p>
			<p>
				<a href="http://ru.wikipedia.org/wiki/Наследование_(программирование)" title="Посмотреть в ВИКИ">Наследование</a> - ситуация, при которой один объект перенимает полностью или частично свойства и модель поведения другого объекта. Предположим, что у нас есть объект “Геометрическая фигура”, который имеет какие-то свойства и функции для работы с ним. Мы хотим создать ещё несколько объектов, таких как “Квадрат”, “Круг”, “Треугольник”. Эти три объекта должны обладать всеми свойствами и функциями геометрической фигуры, поэтому логично было бы заимствовать их у этого объекта.
			</p>
			<p>
				<a href="http://ru.wikipedia.org/wiki/Полиморфизм_(программирование)" title="Посмотреть в ВИКИ">Полиморфизм</a>, в общем случае, - ситуация, при которой в процессе наследования мы переопределяем поведение того или иного объекта. Например, у “Геометрической фигуры” есть функция рисования этой самой фигуры на экране. При наследовании и получении нового объекта - Квадрата, эту функцию необходимо переписать - изменить, чтобы она рисовала именно квадрат.
			</p>
			<p>
				В Python'е полиморфизм связан не с наследованием, а с набором и смыслом доступных функций объекта. <span class="important">Здесь полиморфизмом уже считается тот факт, что в одну и ту же функцию можно передавать значения различных типов данных</span>.
			</p><br/>
			<p>
				Это было своего рода лирическое отступление, а сейчас мы рассмотрим эти принципы и понятия более подробно - то, как они реализуются в Python'е. Учтите, что мы не будем затрагивать тему наследования в данном курсе.
			</p>
			<h3>Основные понятия</h3>
			<p>
				<span class="important" title="Определение"><b>Объект</b> - это нечто, обладающее значением (состоянием), типом (поведением) и индивидуальностью</span>. В общем случае объект представляет из себя набор свойств (<b>атрибутов</b>) и функций для работы с этим объектом (такие функции называют <b>методами</b>).
			</p>
			<p>
				Взаимодействие между объектами заключается в вызове методов одних объектов другими.
			</p>
			<p>
				Даже у значений типа int или float есть свои собственные атрибуты и методы. Рассмотрим следующий пример:
			</p>
			<code>
				<span style="color: #000000;">	
					a = 3<br/>
					b = 4<br/>
					c = a + b<br/>
				</span>
			</code>	
			<p>
				Так как Python по-умолчанию имеет встроенную поддержку типа int, его литералы (числа <cite>3</cite> и <cite>4</cite>) создают в памяти объекты, которые связываются с именами <cite>a</cite> и <cite>b</cite>. После этого над объектами <cite>a</cite> и <cite>b</cite> выполняется операция сложения и имя <cite>c</cite> связывается с получившимся новым объектом.
			</p>
			<p>
				Последнюю строчку можно заменить вызовом определённого метода, который прибавит к объекту <cite>a</cite> объект <cite>b</cite> и вернёт результат в качестве нового объекта:
			</p>
			<code>
				<span style="color: #000000;">	
					c = a.__add__(b)<br/>
				</span>
			</code>	
			<p>
				Здесь <cite>__add__()</cite> - метод объекта <cite>a</cite>, который реализует операцию сложения. У типа int, да и у других типов данных языка Python, есть большое количество различных методов. Чтобы узнать их полный набор для конкретного объекта, можно воспользоваться встроенной функцией <cite>dir()</cite>:
			</p>
			<code>
				<span style="color: #000000;">	
					dir(5) <span class="comment"># Выведет список всех методов объекта</span><br/>
				</span>
			</code>	
			<p>
				Помните, когда нам нужно было преобразовать число в строку, мы использовали встроенную функцию <cite>str()</cite>? В качестве параметра мы передавали объект, который потом преобразовывался в строку. Как это происходило на самом деле: функция <cite>str()</cite> вызывала метод переданного её объекта <cite>__str__()</cite> и возвращала полученный результат:
			</p>
			<code>
				<span style="color: #000000;">	
					a = 5<br/>
					str(a) 		<span class="comment"># '5'</span><br/>
					a.__str__() 	<span class="comment"># '5'	</span><br/>
				</span>
			</code><br/>
			<p>
				Объекты описываются не индивидуально, а с помощью классов. <span class="important" title="Определение"><b>Класс</b> - это шаблон, описание объекта</span>. Созданные на основе классов объекты называют <b>экземпляром</b> класса, то есть объекты <cite>A</cite> и <cite>B</cite> могут быть экземплярами одного класса <cite>Letter</cite>(буква).
			</p>
			<p>
				В языке Python класс объявляется с помощью <span class="important">ключевого слова <b>class</b></span>, за которым стоит имя класса (правила указания идентификаторов справедливы и здесь), после чего идет тело класса, новый блок кода (не забываем про символ <cite>:</cite>).
			</p>
			<code>
				<span style="color: #000000;">	
					<span style="color: #0000FF;">class</span> TestClass:<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">pass</span><br/>
				</span>
			</code>	
			<p>
				После того, как класс был объявлен, допустимо создавать экземпляры этого класса, но никак не ранее:
			</p>
			<code>
				<span style="color: #000000;">	
					testClass = TestClass()<br/>
					<br/>
					errClass = NoClass() <span class="comment"># Ошибка! Класс NoClass не объявлен</span><br/>
				</span>
			</code>	
			<p>
				Заметьте, что хоть класс <cite>TestClass</cite> содержит лишь один оператор <cite>pass</cite>, нет ни данных, ни методов, тем не менее, при создании экземпляра после имени класса стоят пустые круглые скобки.
			</p>
			<br/>
			<p>
				Давайте теперь рассмотрим теперь подробно, что происходит при записи инструкции <cite>testClass = TestClass()</cite>. Правая часть этого выражения, <cite>TestClass()</cite> создает объект типа <cite>TestClass</cite> (экземпляр класса) примерно так же, как и в языке C++ - вызывается специальный метод класса, называемый конструктором (в языке C++), который может быть и не описан. Далее только что созданный объект связывается с именем <cite>testClass</cite>.
			</p>
			<p>
				Как Вы помните, в Python'е операция присваивания копирует не значение объекта, а ссылку на него. В этом легко убедиться на примере нашего класса. До тех пор пока мы не определили, как объекты класса представить строкой, при печати будет выводиться их место в памяти:
			</p>
			<code>
				<span style="color: #000000;">	
					print(testClass)<br/>
					<span class="comment"># Будет выведено что-нибудь, похожее на:</span><br/>
					<span class="comment"># &lt;__main__.TestClass object at 0xb7361ecc&gt;</span><br/>
				</span>
			</code>	
			<p>
				Теперь убедимся, что копируются только ссылки:
			</p>
			<code>
				<span style="color: #000000;">	
					testClass2 = testClass<br/>
					print(testClass2)<br/>
					<span class="comment"># &lt;__main__.TestClass object at 0xb7361ecc&gt;</span><br/>
				</span>
			</code>	
			<p>
				Как видите, вывод идентичен предыдущему - имена ссылаются на один и тот же объект в памяти, поэтому нужно внимательно следить за тем, что и куда вы копируете и изменяете.
			</p>
			<p>
				Стоит отметить, что как и функции, классы в Python'е могут быть объявлены, где угодно. Но лучше всего класса объявлять на уровне модуля, а не в <cite>if</cite>-е, <cite>while</cite>, функции и так далее
			</p>
			<br/>
			<p>
				Теперь давайте рассмотрим пример чуть более содержательный:
			</p>
			<code>
				<span style="color: #000000;">	
					<span style="color: #0000FF;">class</span> MyClass:<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;"""A sample example of class"""<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;i = 12345<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> f(self):<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print('Hello, world!')<br/>
				</span>
			</code>	
			<p>
				Сами классы в Python'е тоже являются объектами (не только их экземпляры). Поэтому через имя класса можно получить доступ к его атрибутам или методам (через оператор <cite>.</cite>, записанный после имени класса):
			</p>
			<code>
				<span style="color: #000000;">	
					print(MyClass.i) <span class="comment"># 12345</span><br/>
					print(MyClass.f) <br/>
					<span class="comment"># Будет выведено что-нибудь вроде:</span><br/>
					<span class="comment"># &lt;function f at 0xb738ab2c&gt;	</span>	<br/>	
				</span>
			</code>	
			<p>
				Давайте теперь подробно рассмотрим, написанный нами код.
			</p>
			<p>
				Сразу же бросается в глаза строка после объявления класса. Она там не просто так - это <b>строка документации</b>. <span class="important">Она должна содержать исчерпывающую информацию о классе, его работе и так далее</span>. <span class="important">Строка документации - это тоже атрибут класса</span>, к которому можно обратиться через специальное имя <cite>__doc__</cite>:
			</p>
			<code>
				<span style="color: #000000;">	
					print(MyClass.__doc__)
					<span class="comment"># A sample example of class</span>
				</span>
			</code>	
			<p>
				Далее идет объявление <cite>i = 12345</cite>. В нем ничего необычного нет, оно означает, что у каждого экземпляра класса (и у самого класса) будет атрибут с именем <cite>i</cite>, которое ссылается на значение <cite>12345</cite>.
			</p>
			<p>
				А вот далее идет объявление функции <cite>f</cite>. И если ее тело не вызывает вопросов (<cite>print('Hello, world!')</cite> напечатает нужную строку), то заголовок, а точнее список аргументов, представляет особый интерес. В нем содержится <span class="important">один аргумент <cite>self</cite>. Это ссылка на текущий экземпляр класса</span>, своего рода аналог указателя this в языке C++. Естественно идентификатор может быть другим, но принято использовать именно <cite>self</cite>. Из-за динамической типизации Python'а атрибуты создаются c указанием <cite>self</cite>, так как в ином случае ссылки на объекты просто теряются.
			</p>
			<p>
				Рассмотрим такой пример:
			</p>
			<code>
				<span style="color: #000000;">	
					<span style="color: #0000FF;">class</span> Point:<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> __init__(self, cX=0, cY=0):<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x = cX<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y = cY<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> printPoint(self):<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print('Point at (%d, %d)' % (x,y))<br/>
					<br/>
					point = Point()<br/>
					point.printPoint()<br/>
				</span>
			</code>	
			<p>
				Будет выведена ошибка: класс <cite>Point</cite> не имеет атрибутов <cite>x</cite> и <cite>y</cite>!
			</p>
			<p>
				Рассмотрим теперь пример более подробно. Функция <cite>__init__()</cite>, как Вы могли подумать, является конструктором. На самом деле, <b>это не так</b>. <span class="important">В Python'е нет такого понятия, как конструктор</span>. <cite>__init__()</cite> - это просто специальная функция, которая вызывается <b>после</b> создании объекта (и чаще всего задает начальное состояние объекта, инициализирует его атрибуты). Но чтобы не пугать программистов на других языках, чаще всего используется эта терминология, конструктор.
			</p>
			<p>
				<cite>__init__()</cite> принимает три аргумента: <cite>self</cite> и две координаты (как Вы помните, можно задавать значение аргументов по-умолчанию). В теле этой функции значения аргументов-координат копируются в переменные <cite>x</cite> и <cite>y</cite>. И можно было бы ожидать, что при вызове функции <cite>printPoint()</cite>, она выведет эти значения на экран, но нет. Произошло это из-за того, что эти переменные <cite>x</cite> и <cite>y</cite> фактически нигде не сохранились - после выхода из <cite>__init__()</cite> доступ к ним теряется. Именно поэтому нужно использовать <cite>self</cite>:
			</p>
			<code>
				<span style="color: #000000;">	
					<span style="color: #0000FF;">class</span> Point:<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> __init__(self, cX=0, cY=0):<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.x = cX<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.y = cY<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> printPoint(self):<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print('Point at (%d, %d)' % (self.x,self.y))<br/>
					<br/>
					point = Point()<br/>
					point.printPoint()<br/>
					<span class="comment"># Point at (0, 0)</span><br/>
				</span>
			</code>	
			<p>
				Команды <cite>self.x = cY</cite> и <cite>self.y = cY</cite> создали у экземпляра нужные нам атрибуты <cite>x</cite> и <cite>y</cite>. А так как функция <cite>printPoint()</cite> вызывается у того же самого экземпляра, для которого была вызвана функция <cite>__init__()</cite>, то у аргумента <cite>self</cite> будут атрибуты <cite>x</cite> и <cite>y</cite>.
			</p>
			<p>
				Далее, как Вы заметили, хоть у обоих функций в списке аргументов есть <cite>self</cite>, мы ни в одну из них ничего не передаем. Интерпретатор автоматически подставляет его, когда нужно.
			</p>
			<div class="note">
				<p>Аргумент <cite>self</cite> должен передаваться <b>во все</b> методы класса!	</p>		
			</div>
			<p>
				При объявлении класса, <span class="important">можно переопределять не только конструктор, но и другие функции-методы</span>. Например, можно реализовать метод преобразования объекта в строку, после чего достаточно будет написать имя-идентификатор, связанное с объектом этого типа, при вызове функции <cite>print()</cite>, чтобы на экран вывелась содержательная информация, а не адрес объекта в памяти.
			</p>
			<p>
				Как вы уже знаете, объекты преобразуются в строку с помощью метода <cite>__str__()</cite>. Осталось добавить реализацию этой функции внутрь создаваемого класса:
			</p>
			<code>
				<span style="color: #000000;">	
					<span style="color: #0000FF;">class</span> Point:<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> __init__(self, cX=0, cY=0):<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.x = cX<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.y = cY<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">def</span> __str__(self): <span class="comment"># Заменим функцию printPoint(self)</span><br/>
					&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #0000FF;">return</span> "%s; %s" % (self.x, self.y)<br/>
					<br/>
					point = Point()<br/>
					print(point) <span class="comment"># Сравните с выводом на экран из предыдущего примера</span><br/>
				</span>
			</code>	
			<p>
				Помимо преобразования в строку, таким образом можно перегрузить различные операции (сложение, умножение, сравнения и другие). Далее приводится соответствие названий некоторых методов, которые необходимо добавить в класс, и операций, которые можно будет использовать и применять к вашим собственным классам:
			</p>		
			<table width="640px" cellpadding="0" cellspacing="0">
				<tr class="lineA head">
					<td class="left">Название метода</td>
					<td>Операция</td>
				</tr>
				<tr class="lineB">
					<td class="left">__init__(self, …)</td>
					<td>Инициализация</td>
				</tr>				
				<tr class="lineA">
					<td class="left">__len__(self)</td>
					<td>Вычисление длины (встроенная функция <cite>len()</cite>, вызывает его)</td>
				</tr>
				<tr class="lineB">
					<td class="left">__str__(self)</td>
					<td>Преобразование в строку</td>
				</tr>
				<tr class="lineA">
					<td class="left">__bool__(self)</td>
					<td>Преобразование к булевому типу</td>
				</tr>
				<tr class="lineB">
					<td class="left">__int__(self)</td>
					<td>Преобразование в целое число</td>
				</tr>
				<tr class="lineB">
					<td class="left">__float__(self)</td>
					<td>Преобразование в число с плавающей точкой</td>
				</tr>
				<tr class="lineA">
					<td class="left">__complex__(self)</td>
					<td>Преобразование в комплексное число</td>
				</tr>
				<tr class="lineB">
					<td class="left">__cmp__(self, other)</td>
					<td>Используется во всех сравнениях</td>
				</tr>
				<tr class="lineA">
					<td class="left">__add__(self, other)</td>
					<td>Сложение: <cite>self + other</cite>, но не <cite>other + self</cite></td>
				</tr>
				<tr class="lineB">
					<td class="left">__radd__(self, other)</td>
					<td>Сложение: <cite>other + self</cite></td>
				</tr>
			</table>			
			<p>
				Учтите, что все эти методы, кроме <cite>__init__()</cite> обязательно должны возвращать значение, и оно должно соответствовать назначению методов!
			</p>
			<p>
				Полный список специальных методов можно найти в документации. Например, <a href="http://docs.python.org/py3k/reference/datamodel.html#emulating-numeric-types" title="Посмотреть документацию">список методов для имитации числовых типов</a>.
			</p>
			<h3>Инкапсуляция</h3>
			<p>
				В языке Python <span class="important">инкапсуляции (то есть сокрытию данных и реализации) не придаётся принципиального значения</span>: её соблюдение <b>зависит от дисциплинированности программиста</b>. В других языках программирования имеются определённые градации доступности методов объекта (private, public, например).
			</p>
			<p>
				Здесь <span class="important">можно получить доступ к любому атрибуту или методу</span> напрямую, если он описан при объявлении класса.
			</p><br/>
			<p>
				Так как язык не позволяет указать правила доступа к тому или иному элементу класса, существует <span class="important">определённое соглашение, которого придерживаются все программисты Python</span>.
			</p>
			<p>
				<span class="important">Знак нижнего подчёркивания <cite>_</cite></span> в начале имени атрибута указывает на то, что он не входит в общедоступный интерфейс, то есть это свойство или метод используются только внутри объекта.
			</p>
			<p>
				<span class="important">Двойное подчёркивание</span> (например, методы <cite>__init__()</cite> или <cite>__str__()</cite>) работает как указание на то, что атрибут - приватный.
			</p>
			<code>
				<span style="color: #000000;">	
					<span style="color: #0000FF;">class</span> X:<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;x = 3<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;_x = 4<br/>
					&nbsp;&nbsp;&nbsp;&nbsp;__x = 5<br/>
					<br/>
					print(dir(X)) <span class="comment"># Будет содержать x, _x и _X__x, но не __x</span>
				</span>
			</code>	
			<hr/>
			<p>
				Помните, что Python в первую очередь объектно-ориентированный язык. Большая часть уже написанных крупных приложений используют возможности классов, чтобы упростить разработку, отладку и поддержку на любом уровне. В этом курсе не рассматривается вся область ООП в Python (простое и множественное наследование, например), так что мы вам рекомендуем ознакомится с дополнительной литературой на данную тематику.
			</p>	
		</div>
		<div class="footer"></div>
	</body>
</html>